package com.repo.crash;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 全局捕获闪退，闪退之后会写入到包缓存getCrash() crashHandler文件夹下面
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = CrashHandler.class.getSimpleName();
    private static CrashHandler INSTANCE = new CrashHandler();
    private Context mContext;
    private Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
    private String mLodPath;
    private final String dataFormatPattern = "yyyy-MM-dd-HH:mm:ss";
    private final String mFilePrefix = "/crashLog/";
    private final String mFileName = ".txt";
    private final long filesCacheTime = 7 * 86400000L;//7天

    @Override
    public void uncaughtException(Thread t, Throwable e) {//当发生exception时候会回调该方法
        dumpToSDCard(t, e);//dump trace 信息到sd卡
        //todo 上传服务器
        e.printStackTrace();
        if (mDefaultExceptionHandler != null) { //交给系统的UncaughtExceptionHandler处理
            mDefaultExceptionHandler.uncaughtException(t, e);
        } else {
            Process.killProcess(Process.myPid());
        }
    }

    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
        return INSTANCE;
    }

    public void init(Context context) {
        this.mContext = context.getApplicationContext();
        // 获取系统默认的UncaughtException处理器
        mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();//获取当前默认ExceptionHandler，保存在全局对象

        // 设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);//替换默认对象为当前对象


        //初始化路径
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
                mContext.getExternalCacheDir() == null) {
            Log.i(TAG, "no sdcard skip dump 存储未挂载");
            mLodPath = mContext.getCacheDir().getAbsolutePath() + mFilePrefix;
            return;
        }
        mLodPath = mContext.getExternalCacheDir().getAbsolutePath() + mFilePrefix;

        deleteDueLogs(mLodPath, filesCacheTime);
    }

    /**
     * 写入日志到文件
     *
     * @param t 即将退出的线程。
     * @param e 捕获的异常。
     */
    private void dumpToSDCard(final Thread t, final Throwable e) {

        if (mLodPath == null) {
            return;
        }
        File file = new File(mLodPath);
        if (!file.exists()) {
            file.mkdirs();
        }


        String time = new SimpleDateFormat(dataFormatPattern, Locale.CHINA).format(new Date(System.currentTimeMillis()));
        Log.i(TAG, mLodPath + time + mFileName);
        File logFile = new File(mLodPath, time + mFileName);

        try {
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(logFile)));
            //写入时间
            pw.println(time);
            //获取手机信息写入
            dumpPhoneInfo(pw);
            //线程信息
            pw.print("Thread info : ");
            pw.print(t.toString());
            //换行
            pw.println();
            //异常栈trace捕获写入
            e.printStackTrace(pw);
            //关闭写入笔
            pw.close();

        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }

    /**
     * 获取手机信息写入日志头部方便查看和查询问题
     *
     * @param pw 写入
     */
    private void dumpPhoneInfo(PrintWriter pw) {
        //应用的版本名称和版本号
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = null;
        try {
            pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                //App 版本
                pw.print("App Version: ");
                pw.print(pi.versionName);
                pw.print(" _ code:");
                pw.println(pi.versionCode);

                //android版本号
                pw.print("OS Version: ");
                pw.print(Build.VERSION.RELEASE);
                pw.print("_");
                pw.println(Build.VERSION.SDK_INT);

                //手机制造商
                pw.print("Vendor: ");
                pw.println(Build.MANUFACTURER);

                //手机型号
                pw.print("Model: ");
                pw.println(Build.MODEL);

                //cpu架构
                pw.print("CPU ABI: ");
                pw.println(Build.CPU_ABI);
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void zip(String src, String dest) throws IOException { //压缩文件夹，为上传做准备。节省流量。
        ZipOutputStream out = null;
        File outFile = new File(dest);
        File fileOrDirectory = new File(src);
        out = new ZipOutputStream(new FileOutputStream(outFile));
        if (fileOrDirectory.isFile()) {
            zipFileOrDirectory(out, fileOrDirectory, "");
        } else {
            File[] entries = fileOrDirectory.listFiles();
            for (int i = 0; i < entries.length; i++) {
                zipFileOrDirectory(out, entries[i], "");
            }
        }
        if (null != out) {
            out.close();
        }
    }

    private static void zipFileOrDirectory(ZipOutputStream out, File fileOrDirectory, String curPath) throws IOException {
        FileInputStream in = null;
        if (!fileOrDirectory.isDirectory()) {
            byte[] buffer = new byte[4096];
            int bytes_read;
            in = new FileInputStream(fileOrDirectory);
            ZipEntry entry = new ZipEntry(curPath + fileOrDirectory.getName());
            out.putNextEntry(entry);
            while ((bytes_read = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytes_read);
            }
            out.closeEntry();
        } else {
            File[] entries = fileOrDirectory.listFiles();
            for (int i = 0; i < entries.length; i++) {
                zipFileOrDirectory(out, entries[i], curPath + fileOrDirectory.getName() + "/");
            }
        }
        if (null != in) {
            in.close();
        }
    }

    private final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();


    /**
     * 删除生成的日志文件，根据当前时间和日志时间进行对比如若时间大于设定的时间时则删除文件，防止缓存过大！
     * 文件格式 app/cache/crashLog/yyyy-MM-dd-HH:mm:ss.txt
     *
     * @param filePath 传入文件夹的目录路径
     * @param time     设置缓存时间超过时间则删除
     */
    private void deleteDueLogs(String filePath, long time) {
        File file = new File(filePath);
        File[] files = file.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}.txt$");
            }
        });
        if (files == null || files.length <= 0) return;
        final SimpleDateFormat sdf = new SimpleDateFormat(dataFormatPattern, Locale.getDefault());
        try {
            for (final File aFile : files) {
                String name = aFile.getName();
                int l = name.length();
                String logDay = name.substring(l - 23, l - 4);
                long curTime = System.currentTimeMillis();
                long dueMillis = (curTime - sdf.parse(logDay).getTime());
                if (dueMillis >= time) {//文件超过七天删除
                    EXECUTOR.execute(new Runnable() {
                        @Override
                        public void run() {
                            boolean delete = aFile.delete();
                            if (!delete) {
                                Log.e("DLog", "delete " + aFile + " failed!");
                            }
                        }
                    });
                }
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}